//@author A0100939H

#include "Tokeniser.h"

const char* Tokeniser::KEYWORD_AT = "\\bat\\b";
const char* Tokeniser::KEYWORD_ON = "\\bon\\b";
const char* Tokeniser::KEYWORD_BY = "\\bby\\b";
const char* Tokeniser::KEYWORD_FROM = "\\bfrom\\b";
const char* Tokeniser::KEYWORD_TO = "\\bto\\b";

const char* Tokeniser::KEYWORD_EDIT_NAME = "n";
const char* Tokeniser::KEYWORD_EDIT_START_TIME = "st";
const char* Tokeniser::KEYWORD_EDIT_END_TIME = "et";
const char* Tokeniser::KEYWORD_EDIT_START_DATE = "sd";
const char* Tokeniser::KEYWORD_EDIT_END_DATE = "ed";

const string Tokeniser::DATE = "date";
const string Tokeniser::TIME = "time";
const string Tokeniser::DATE_TIME_INVALID = "INVALID";
const string Tokeniser::TODAY = "today";
const string Tokeniser::TOMORROW = "tomorrow";

const string Tokeniser::QUOTE = "\"";
const string Tokeniser::STRING_EMPTY = "";
const int Tokeniser::SIZE_VECTOR = 5;

const QTime Tokeniser::TIME_MIDNIGHT = QTime::fromString("0000", "hhmm");
const QTime Tokeniser::TIME_BEFORE_MIDNIGHT = QTime::fromString("2359", "hhmm");

const char* Tokeniser::DATE_TYPE_ARMY = "dMyy";
const char* Tokeniser::DATE_TYPE_SHORT_TYPE_1 = "d/M";
const char* Tokeniser::DATE_TYPE_SHORT_TYPE_2 = "d/M/yy";
const char* Tokeniser::DATE_TYPE_DAY_TYPE_1 = "dddd";
const char* Tokeniser::DATE_TYPE_DAY_TYPE_2 = "ddd";

const QDate Tokeniser::DATE_CURRENT = QDate::currentDate();
const char* Tokeniser::DATE_FORMAT_ARMY = "((\\d{1,2})(\\d{1,2})(\\d{2}))"; //eg 120514
const char* Tokeniser::DATE_FORMAT_SHORT_TYPE_1 = "((\\d{1,2}/(\\d{1,2})))"; //eg 12/03
const char* Tokeniser::DATE_FORMAT_SHORT_TYPE_2 = "((\\d{1,2})/(\\d{1,2})/(\\d{2}))"; //eg 12/03/14
const char* Tokeniser::DATE_FORMAT_DAY = "(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)"; //eg mon, monday Monday, THU
const char* Tokeniser::DATE_TODAY = "(\\btoday\\b)|(\\btdy\\b)";
const char* Tokeniser::DATE_TOMORROW = "(\\btomorrow\\b)|(\\btml\\b)|(\\btmr\\b)";

const char* Tokeniser::TIME_TYPE_12_HOUR = "hh:mmap";
const char* Tokeniser::TIME_TYPE_24_HOUR_TYPE_1 = "hhmm";
const char* Tokeniser::TIME_TYPE_24_HOUR_TYPE_2 = "hh:mm";

const char* Tokeniser::TIME_FORMAT_12_HOUR = "(0[1-9]|1[0-2])(?:[:]?(\\d{2}))?\\s?(am|pm)"; //eg 05:30am
const char* Tokeniser::TIME_FORMAT_24_HOUR_TYPE_1 = "((?:\\d{2})(\\d{2}))?"; //eg 0830
const char* Tokeniser::TIME_FORMAT_24_HOUR_TYPE_2 = "((?:\\d{2}):(\\d{2}))?"; //eg 08:30

const char* Tokeniser::CMD_FORMAT_QUOTE_TYPE_1 = "\".+\"$"; //eg "tuition"
const char* Tokeniser::CMD_FORMAT_QUOTE_TYPE_2 = "\".+\" .+$"; //eg "tuition" tmr
const char* Tokeniser::CMD_FORMAT_FLOATING = "((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b)).)+$";

const char* Tokeniser::CMD_FORMAT_ADD_FLOATING = "add (((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b|\\btmr\\b|\\btomorrow\\b|\\btml\\b|\\btoday\\b|\\btdy\\b)).)+|(\".+\"))$"; //add (anything) or add "anything"
const char* Tokeniser::CMD_FORMAT_ADD_DATE = "add (((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b|\\btmr\\b|\\btomorrow\\b|\\btml\\b|\\btoday\\b|\\btdy\\b)).)+|(\".+\")) (on|by) ((today|tdy)|(tomorrow|tml|tmr)|((\\d{1,2})(\\d{1,2})(\\d{2}))|((\\d{1,2})/(\\d{1,2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?))$"; // add sth/"sth" by/on <date>
const char* Tokeniser::CMD_FORMAT_ADD_DATETIME = "add (((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b|\\btmr\\b|\\btomorrow\\b|\\btml\\b|\\btoday\\b|\\btdy\\b)).)+|(\".+\")) (on|by) ((today|tdy)|(tomorrow|tml|tmr)|((\\d{1,2})(\\d{1,2})(\\d{2}))|((\\d{1,2})/(\\d{1,2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?))( at)? (((0[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2})))$"; //add sth/"sth" by/on <date> (at) <time>
const char* Tokeniser::CMD_FORMAT_ADD_TIME = "add (((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b|\\btmr\\b|\\btomorrow\\b|\\btml\\b|\\btoday\\b|\\btdy\\b)).)+|(\".+\")) at (((0[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2})))$"; //add sth/"sth" at <time>
const char* Tokeniser::CMD_FORMAT_ADD_TIMETIME = "add (((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b|\\btmr\\b|\\btomorrow\\b|\\btml\\b|\\btoday\\b|\\btdy\\b)).)+|(\".+\")) from (((0[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))) to (((0[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2})))$"; //add sth/"sth" from <time> to <time>
const char* Tokeniser::CMD_FORMAT_ADD_DATEDATE = "add (((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b|\\btmr\\b|\\btomorrow\\b|\\btml\\b|\\btoday\\b|\\btdy\\b)).)+|(\".+\")) from ((today|tdy)|(tomorrow|tml|tmr)|((\\d{1,2})(\\d{1,2})(\\d{2}))|((\\d{1,2})/(\\d{1,2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) to ((today|tdy)|(tomorrow|tml|tmr)|((\\d{1,2})(\\d{1,2})(\\d{2}))|((\\d{1,2})/(\\d{1,2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?))$"; //add sth/"sth" from <date> to <date>
const char* Tokeniser::CMD_FORMAT_ADD_DATETIMETIME = "add (((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b|\\btmr\\b|\\btomorrow\\b|\\btml\\b|\\btoday\\b|\\btdy\\b)).)+|(\".+\")) on ((today|tdy)|(tomorrow|tml|tmr)|((\\d{1,2})(\\d{1,2})(\\d{2}))|((\\d{1,2})/(\\d{1,2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) from (((0[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))) to (((0[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2})))$"; //add sth/"sth" on <date> from <time> to <time>
const char* Tokeniser::CMD_FORMAT_ADD_DATETIMEDATETIME = "add (((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b|\\btmr\\b|\\btomorrow\\b|\\btml\\b|\\btoday\\b|\\btdy\\b)).)+|(\".+\")) from ((today|tdy)|(tomorrow|tml|tmr)|((\\d{1,2})(\\d{1,2})(\\d{2}))|((\\d{1,2})/(\\d{1,2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) (((0[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))) to ((today|tdy)|(tomorrow|tml|tmr)|((\\d{1,2})(\\d{1,2})(\\d{2}))|((\\d{1,2})/(\\d{1,2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) (((0[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2})))$"; //add sth/"sth" from <date> <time> to <date> <time>
const char* Tokeniser::CMD_FORMAT_ADD_RELATIVEDAY_TYPE_1 = "add (((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b|\\btmr\\b|\\btomorrow\\b|\\btml\\b|\\btoday\\b|\\btdy\\b)).)+|(\".+\")) (today|tdy|tmr|tml|tomorrow)"; //add sth/"sth" <relative day>
const char* Tokeniser::CMD_FORMAT_ADD_RELATIVEDAY_TYPE_2 =	"add (((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b|\\btmr\\b|\\btomorrow\\b|\\btml\\b|\\btoday\\b|\\btdy\\b)).)+|(\".+\")) (today|tdy|tmr|tml|tomorrow) at (((0[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2})))"; //add sth/"sth" <relative day> at <time> 
const char* Tokeniser::CMD_FORMAT_ADD_RELATIVEDAY_TYPE_3 = "add (((?!((\\bon\\b)|\\bat\\b|\\bby\\b|\\bfrom\\b|\\bto\\b|\\btmr\\b|\\btomorrow\\b|\\btml\\b|\\btoday\\b|\\btdy\\b)).)+|(\".+\")) (today|tdy|tmr|tml|tomorrow) from (((0[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))) to (((0[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2})))";//add sth/"sth" <relative day> from <time> to <time>
const char* Tokeniser::CMD_FORMAT_ADD_QUOTE = "add \".+\"$";

const char* Tokeniser::CMD_FORMAT_EDIT_NAME = "edit [1-9][0-9]* n .+$";
const char* Tokeniser::CMD_FORMAT_EDIT_TIME = "edit [1-9][0-9]* (st|et) (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2})))$";
const char* Tokeniser::CMD_FORMAT_EDIT_DATE = "edit [1-9][0-9]* (sd|ed) ((today|tdy)|(tomorrow|tml|tmr)|((\\d{1,2})(\\d{1,2})(\\d{2}))|((\\d{1,2})/(\\d{1,2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?))$";

const char* Tokeniser::CMD_FORMAT_DELETE = "delete [1-9][0-9]*$";
const char* Tokeniser::CMD_FORMAT_DISPLAY = "(display(?:[w|t|e]?))|displaydone|displayall|(displayd(b)? ((today|tdy)|(tomorrow|tml|tmr)|((\\d{1,2})(\\d{1,2})(\\d{2}))|((\\d{1,2})/(\\d{1,2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)))$";
const char* Tokeniser::CMD_FORMAT_SEARCH = "search .+$";
const char* Tokeniser::CMD_FORMAT_REDO = "\\bredo\\b";
const char* Tokeniser::CMD_FORMAT_UNDO = "\\bundo\\b";
const char* Tokeniser::CMD_FORMAT_HELP = "(\\bhelp\\b)|(help (add|display|edit|search|undo|redo|clear|date|time|done|exit))";
const char* Tokeniser::CMD_FORMAT_CLEAR = "\\bclear\\b";
const char* Tokeniser::CMD_FORMAT_EXIT = "\\bexit\\b";
const char* Tokeniser::CMD_FORMAT_DONE = "\\bdone\\b [1-9][0-9]*$";
const char* Tokeniser::CMD_FORMAT_EVENT = "(\".+\" (on|at|from|tmr|tml|tomorrow|today|tdy) .+)|(\".+\" (tmr|tml|tomorrow|today|tdy))|(\".+\")";

///////////////////////
///PRIVATE FUNCTIONS///
///////////////////////

//FUNCTIONS TO EXTRACT ENTRY//

CommonEnum::COMMAND_TYPE Tokeniser::extractCommand() {

	if(!checkFormat(_textLine)) {
		throw BotException(BotException::EXCEPTION_FORMAT_INVALID);
	}

    string command;

    stringstream temp(_textLine);
    temp >> command >> ws;
	_textLine.clear();
    getline(temp, _textLine);

	command = CommonUtils::toLower(command);
 	if(command == CommonUtils::STRING_COMMAND_ADD){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_ADD;
 	} else if(command == CommonUtils::STRING_COMMAND_DELETE){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_DELETE;
 	} else if(command == CommonUtils::STRING_COMMAND_SET_DONE){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_SET_DONE;
 	} else if(command == CommonUtils::STRING_COMMAND_EDIT){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_EDIT;
 	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_DISPLAY;
 	}else if (command == CommonUtils::STRING_COMMAND_DISPLAY_ALL){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_DISPLAY_ALL;
 	}else if(command == CommonUtils::STRING_COMMAND_SEARCH){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_SEARCH;
 	}else if(command == CommonUtils::STRING_COMMAND_UNDO){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_UNDO;
 	}else if(command == CommonUtils::STRING_COMMAND_REDO){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_REDO;
 	}else if (command == CommonUtils::STRING_COMMAND_DISPLAY_TASK){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_DISPLAY_TASK;
 	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_EVENT){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_DISPLAY_EVENT;
 	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_WEEK){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_DISPLAY_WEEK;
 	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_DAY){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_DISPLAY_DAY;
 	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_DAY_BEFORE){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_DISPLAY_DAY_BEFORE;
 	}else if (command == CommonUtils::STRING_COMMAND_CLEAR){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_CLEAR;
 	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_DONE){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_DISPLAY_DONE;
 	} else if (command == CommonUtils::STRING_COMMAND_EXIT){
		_command = CommonEnum::COMMAND_TYPE::COMMAND_EXIT;
 	} else {
		_command = CommonEnum::COMMAND_TYPE::COMMAND_INVALID;
	}

	return _command;
 }

void Tokeniser::extractAndPackage(vector <string>& v) {

	string relativeDay;
	QRegExp quote1Format(CMD_FORMAT_QUOTE_TYPE_1, Qt::CaseInsensitive);
    QRegExp quote2Format(CMD_FORMAT_QUOTE_TYPE_2, Qt::CaseInsensitive);
    QString qstr = QString::fromStdString(_textLine);

        if(quote1Format.exactMatch(qstr)) {
            v.push_back(extractKey());
		} else if(checkNumOfKeyword(_textLine) == 0) {
			if(checkRelativeDay(_textLine)) {
                relativeDay = extractRelativeDay(_textLine);
                v.push_back(extractKey());
                v.push_back(relativeDay);
            } else {
                v.push_back(extractKey());
            }
        } else if(quote2Format.exactMatch(qstr)) {
			v.push_back(extractNameInQuote(_textLine));
			   
             if(checkRelativeDay(_textLine)) {
                  string tmp = _textLine;
                  relativeDay = extractRelativeDay(tmp);
                  v.push_back(relativeDay);
				  _textLine = extractFirstWord(_textLine);
              }

				  _textLine = extractFirstWord(_textLine);
                  int num = checkNumOfKeyword(_textLine);
                  while(num > 0){
                      v.push_back(extractName(_textLine));
					  _textLine = extractFirstWord(_textLine);

                      --num;
                  }

                  if(!_textLine.empty()) {
                      v.push_back(_textLine);
                  }
		} else {
			string tmp = _textLine;
			string name = extractName(tmp);

            if(checkRelativeDay(name)) {
                relativeDay = extractRelativeDay(name); 
                v.push_back(name);
                v.push_back(relativeDay);
				_textLine = extractFirstWord(tmp);
            }
			
			int num = checkNumOfKeyword(_textLine);
            while(num > 0){
                v.push_back(extractName(_textLine)); 
				_textLine = extractFirstWord(_textLine);

                --num;
            }

            if(!_textLine.empty()) {
                v.push_back(_textLine);
            }
        }	
}

void Tokeniser::extractTask(vector<string> v) {

	if(v.size() == 2) {
		vector<string> temp=splitToWords(v[1]);
		if(temp.size() > 2) {
			throw BotException(BotException::EXCEPTION_DATE_INVALID);
		} else {
			if(checkDateOrTime(temp[0]) == TIME) {
				_newEntry->setEndTime(formatTime(temp[0]), CommonEnum::CALLER_TOKENISER);
			} else if(checkDateOrTime(temp[0]) == DATE) {
				_newEntry->setEndDate(formatDate(temp[0]), CommonEnum::CALLER_TOKENISER);
			}
			
			if(temp.size() == 2) {
				if(checkDateOrTime(temp[1]) == TIME) {

					if(_newEntry->getEndTime().isValid()) {
						throw BotException(BotException::EXCEPTION_TIME_INVALID);
					}

						_newEntry->setEndTime(formatTime(temp[1]), CommonEnum::CALLER_TOKENISER);

					} else if(checkDateOrTime(temp[1]) == DATE) {
						if(_newEntry->getEndDate().isValid()) {
							throw BotException(BotException::EXCEPTION_DATE_INVALID);
						}

						_newEntry->setEndDate(formatDate(temp[1]), CommonEnum::CALLER_TOKENISER);
					}
				}
			}
	} else if(v.size() == 3) {
			vector<string> temp=splitToWords(v[1]);

			if(temp.size() > 1) {
				throw BotException(BotException::EXCEPTION_DATE_INVALID); 
			}

			if(checkDateOrTime(temp[0]) == TIME) {
				_newEntry->setEndTime(formatTime(temp[0]), CommonEnum::CALLER_TOKENISER);
			} else if(checkDateOrTime(temp[0]) == DATE) {
				_newEntry->setEndDate(formatDate(temp[0]), CommonEnum::CALLER_TOKENISER);
			}

			temp.clear();
			temp = splitToWords(v[2]);

			if(temp.size() > 1) {
				throw BotException(BotException::EXCEPTION_DATE_INVALID);
			}

			if(checkDateOrTime(temp[0]) == TIME) {
				if(_newEntry->getEndTime().isValid()) {
					throw BotException(BotException::EXCEPTION_TIME_INVALID); 
				}

				_newEntry->setEndTime(formatTime(temp[0]), CommonEnum::CALLER_TOKENISER);
			} else if(checkDateOrTime(temp[0]) == DATE) {
				if(_newEntry->getEndDate().isValid()) {
					throw BotException(BotException::EXCEPTION_DATE_INVALID);
				}

				_newEntry->setEndDate(formatDate(temp[0]), CommonEnum::CALLER_TOKENISER);
			}
		}
		else {
			throw BotException(BotException::EXCEPTION_DATE_INVALID);
		}
}

void Tokeniser::extractEvent(vector<string> v) {

	QRegExp dayDateFormat(DATE_FORMAT_DAY, Qt::CaseInsensitive);
    bool type = false;
    QDate startDate;

	if(v.size() == 2){
			vector<string> temp=splitToWords(v[1]);
			if(temp.size()>2) {
				throw BotException(BotException::EXCEPTION_DATE_INVALID);
			}
			
			if(checkDateOrTime(temp[0]) == TIME) {
				_newEntry->setEndTime(formatTime(temp[0]), CommonEnum::CALLER_TOKENISER);
			} else if(checkDateOrTime(temp[0]) == DATE) {
				_newEntry->setEndDate(formatDate(temp[0]), CommonEnum::CALLER_TOKENISER);
			}

			if(temp.size() == 2) {
				if(checkDateOrTime(temp[1]) == TIME) {
					if(_newEntry->getEndTime().isValid()) {
						throw BotException(BotException::EXCEPTION_TIME_INVALID);
					}
					
					_newEntry->setEndTime(formatTime(temp[1]), CommonEnum::CALLER_TOKENISER);
				} else if(checkDateOrTime(temp[1]) == DATE) {
					if(_newEntry->getEndDate().isValid()) {
						throw BotException(BotException::EXCEPTION_DATE_INVALID);
					}

					_newEntry->setEndDate(formatDate(temp[1]), CommonEnum::CALLER_TOKENISER);
				}
			}
			
			_newEntry->setStartDate(_newEntry->getEndDate(), CommonEnum::CALLER_TOKENISER);
			_newEntry->setStartTime(_newEntry->getEndTime(), CommonEnum::CALLER_TOKENISER);

		} else if(v.size() == 3) {
			if(checkDateOrTime(v.at(1)) == DATE && checkDateOrTime(v.at(2)) == TIME) {
				_newEntry->setStartDate(formatDate(v.at(1)), CommonEnum::CALLER_TOKENISER);
				_newEntry->setEndDate(formatDate(v.at(1)), CommonEnum::CALLER_TOKENISER);
				_newEntry->setStartTime(formatTime(v.at(2)), CommonEnum::CALLER_TOKENISER);
				_newEntry->setEndTime(formatTime(v.at(2)), CommonEnum::CALLER_TOKENISER);
			}

			vector<string> temp = splitToWords(v[1]);
			if(checkDateOrTime(temp[0]) == TIME) {
				_newEntry->setStartTime(formatTime(temp[0]), CommonEnum::CALLER_TOKENISER);
			} else if(checkDateOrTime(temp[0]) == DATE) {
				_newEntry->setStartDate(formatDate(temp[0]), CommonEnum::CALLER_TOKENISER);
			}

			if(temp.size() == 2)
				if(checkDateOrTime(temp[1]) == TIME) { 
					if(_newEntry->getStartTime().isValid()) {
						throw BotException(BotException::EXCEPTION_TIME_INVALID);
					}

					_newEntry->setStartTime(formatTime(temp[1]), CommonEnum::CALLER_TOKENISER);
				} else if(checkDateOrTime(temp[1]) == DATE) {
					if(_newEntry->getStartDate().isValid()) {
						throw BotException(BotException::EXCEPTION_DATE_INVALID);
					}

					_newEntry->setStartDate(formatDate(temp[1]), CommonEnum::CALLER_TOKENISER);
				}

			temp.clear();
			temp = splitToWords(v[2]);

			if(checkDateOrTime(temp[0]) == TIME) {
				if(!_newEntry->getStartTime().isValid()) {
					_newEntry->setStartTime(formatTime(temp[0]), CommonEnum::CALLER_TOKENISER);
				}

				_newEntry->setEndTime(formatTime(temp[0]), CommonEnum::CALLER_TOKENISER);
			} else if(checkDateOrTime(temp[0]) == DATE) {
				_newEntry->setEndDate(formatDate(temp[0]), CommonEnum::CALLER_TOKENISER); 
			}

			if(temp.size() == 2) {
				if(checkDateOrTime(temp[1]) == TIME) {
					if(_newEntry->getEndTime().isValid()) {
						throw BotException(BotException::EXCEPTION_TIME_INVALID);
					}

					_newEntry->setEndTime(formatTime(temp[1]), CommonEnum::CALLER_TOKENISER);
				} else if(checkDateOrTime(temp[0]) == DATE) {
					if(_newEntry->getEndDate().isValid()) {
						throw BotException(BotException::EXCEPTION_DATE_INVALID);
					}

					_newEntry->setEndDate(formatDate(temp[1]), CommonEnum::CALLER_TOKENISER);
				}

			}
		} else if(v.size() == 4) {
			vector<string> temp=splitToWords(v[1]);
			
			if(temp.size() > 1) {
				throw BotException(BotException::EXCEPTION_DATE_INVALID);
			}

			if(checkDateOrTime(temp[0]) == DATE) {
				_newEntry->setStartDate(formatDate(temp[0]), CommonEnum::CALLER_TOKENISER);
				_newEntry->setEndDate(formatDate(temp[0]), CommonEnum::CALLER_TOKENISER);
			} else {
				throw BotException(BotException::EXCEPTION_DATE_INVALID);
			}

			temp.clear();
			temp = splitToWords(v[2]);

			if(temp.size()>1) {
				throw BotException(BotException::EXCEPTION_TIME_INVALID);
			}

			if(checkDateOrTime(temp[0]) == TIME) {
				_newEntry->setStartTime(formatTime(temp[0]), CommonEnum::CALLER_TOKENISER);
			} else {
				throw BotException(BotException::EXCEPTION_TIME_INVALID);
			}

			temp.clear();
			temp = splitToWords(v[3]);

			if(temp.size()>1) {
				throw BotException(BotException::EXCEPTION_TIME_INVALID); 
			}

			if(checkDateOrTime(temp[0]) == TIME) {
				_newEntry->setEndTime(formatTime(temp[0]), CommonEnum::CALLER_TOKENISER);
			} else {
				throw BotException(BotException::EXCEPTION_TIME_INVALID);
			}
		}
}

void Tokeniser::validateEntry() {

	if((_newEntry->getStartDate().isValid()) && (!_newEntry->getStartTime().isValid())) {
		_newEntry->setStartTime(TIME_MIDNIGHT, CommonEnum::CALLER_TOKENISER);
	}

	if((!_newEntry->getStartDate().isValid()) && (_newEntry->getStartTime().isValid())) {
		_newEntry->setStartDate(QDate::currentDate(), CommonEnum::CALLER_TOKENISER);
	}

	if((_newEntry->getEndDate().isValid()) && (!_newEntry->getEndTime().isValid())) {
		_newEntry->setEndTime(TIME_BEFORE_MIDNIGHT, CommonEnum::CALLER_TOKENISER);
	}
	
	if((!_newEntry->getEndDate().isValid()) && (_newEntry->getEndTime().isValid())) {
		if(_newEntry->getStartDate().isValid()) {
			_newEntry->setEndDate(_newEntry->getStartDate(), CommonEnum::CALLER_TOKENISER);
		} else {
			_newEntry->setEndDate(QDate::currentDate(), CommonEnum::CALLER_TOKENISER);
		}
	}

	if((_newEntry->getStartDate().isValid()) && (_newEntry->getStartTime().isValid()) && (_newEntry->getEndDate().isValid()) && (_newEntry->getEndTime().isValid())) {
		if(QDateTime(_newEntry->getStartDate(),_newEntry->getStartTime()) > QDateTime(_newEntry->getEndDate(), _newEntry->getEndTime())) {
			throw BotException(BotException::EXCEPTION_START_END_CONFLICT);
		}
	}
}

Entry* Tokeniser::extractEntry() {
	assert(_command == CommonEnum::COMMAND_ADD);

	vector <string> v;
	regex byKeyword(KEYWORD_BY, regex::icase);
	regex eventFormat(CMD_FORMAT_EVENT, regex::icase);

	if(regex_match(_textLine, eventFormat)) {
		_newEntry = new Event;
	} else if(regex_search(_textLine, byKeyword)) {
		_newEntry = new Task;
	} else {
		_newEntry = new Event;
	}

    extractAndPackage(v);

	if(isEmpty(v[0])){
		throw BotException(BotException::EXCEPTION_NAME_INVALID);
	}

    _newEntry->setName(v.at(0));

	if(_newEntry->getEntryType() == CommonUtils::ENTRY_TYPE_TASK){
		extractTask(v);
	} else if(_newEntry->getEntryType() == CommonUtils::ENTRY_TYPE_EVENT) {
		extractEvent(v);
	}

	validateEntry();

	return _newEntry;
}

//SUPPORTING FUNCTIONS FOR EXTRACTING ENTRY//

QTime Tokeniser::formatTime(string timeString) {

    QRegExp time12HourFormat(TIME_FORMAT_12_HOUR, Qt::CaseInsensitive);
    QRegExp time24HourFormat1(TIME_FORMAT_24_HOUR_TYPE_1);
    QRegExp time24HourFormat2(TIME_FORMAT_24_HOUR_TYPE_2);

    QString qstr = QString::fromStdString(timeString);

    QTime time;

    if(time24HourFormat1.exactMatch(qstr)) {
		time = QTime::fromString(QString::fromStdString(timeString), TIME_TYPE_24_HOUR_TYPE_1);
    } else if(time24HourFormat2.exactMatch(qstr)) {
        time = QTime::fromString(QString::fromStdString(timeString), TIME_TYPE_24_HOUR_TYPE_2);
    } else if(time12HourFormat.exactMatch(qstr)) {
        time = QTime::fromString(QString::fromStdString(timeString), TIME_TYPE_12_HOUR);
    } 

	if(!time.isValid()) {
		throw BotException(BotException::EXCEPTION_TIME_INVALID);
	}

    return time;
}

bool Tokeniser::isEmpty(string str) {

	for(int i=0; i<str.size(); i++){
		if(!isspace(str[i])){
			return false;
		}
	}
	return true;
}

string Tokeniser::extractFirstWord(string str) {

	string tempStr;
	stringstream temp(str);
    temp >> tempStr >> ws;
	str.clear();
    getline(temp, str);

	return str;
}

string Tokeniser::extractNameInQuote(string& str) {

	unsigned pos1 = str.find_first_of(QUOTE);
    unsigned pos2 = str.find_first_of(QUOTE, pos1 + 1);	

	if(pos1 == string::npos || pos2 == string::npos) {
		throw BotException(BotException::EXCEPTION_NAME_INVALID);
	} 
		
	string name = str.substr(pos1 + 1, pos2 - pos1 -1);

	if(!isEmpty(_textLine)) {
	_textLine = str.substr(pos2 + 1);
	}

	return name;
}

string Tokeniser::extractName(string& textLine) {

    string name;
	smatch m;

    vector <string> v;
	v.push_back(KEYWORD_BY);
	v.push_back(KEYWORD_ON);
	v.push_back(KEYWORD_AT);
	v.push_back(KEYWORD_FROM);
	v.push_back(KEYWORD_TO);

    int i = 0;
    unsigned pos = string::npos; 

    do{
		regex temp(v.at(i), regex::icase);
		if(regex_search(textLine, m, temp)) {
			pos = m.position(0);
		} else {
			++i;
		}
    } while(i < 5 && pos == std::string::npos);
   
	if(pos-1 == string::npos){
		throw BotException(BotException::EXCEPTION_NAME_INVALID);
 	} else {
		name = textLine.substr(0, pos - 1);
		textLine = textLine.substr(pos - 1);
	}

	if(isEmpty(name)){
		throw BotException(BotException::EXCEPTION_NAME_INVALID);
 	}

    return name;
}

string Tokeniser::extractRelativeDay(string& textLine) {

	regex todayFormat(DATE_TODAY, regex::icase);
	regex tomorrowFormat(DATE_TOMORROW, regex::icase);
	unsigned pos;
	string relativeDay;
	smatch m;

	if(regex_search(textLine, todayFormat)) {
		regex_search (textLine, m, todayFormat);
		pos = m.position(0);
		pos--;
		relativeDay = TODAY;
	} else if(regex_search(textLine, tomorrowFormat)) {
		regex_search (textLine, m, tomorrowFormat);
		pos = m.position(0);
		pos--;
		relativeDay = TOMORROW;
	} 

	if(pos != string::npos) {
		textLine = textLine.substr(0, pos);
	} else {
		throw BotException(BotException::EXCEPTION_NAME_INVALID);
	}

	return relativeDay;
}

int Tokeniser::checkNumOfKeyword(string &textLine) {

	regex byKeyword(KEYWORD_BY, regex::icase);
	regex onKeyword(KEYWORD_ON, regex::icase);
	regex atKeyword(KEYWORD_AT, regex::icase);
	regex fromKeyword(KEYWORD_FROM, regex::icase);
	regex toKeyword(KEYWORD_TO, regex::icase);

    int numOfKeyword = 0;

	if(regex_search(textLine, byKeyword)) {
        ++numOfKeyword;
    }
    if(regex_search(textLine, onKeyword)) {
        ++numOfKeyword;
    }
    if(regex_search(textLine, atKeyword)) {
        ++numOfKeyword;
    }
    if(regex_search(textLine, fromKeyword)) {
        ++numOfKeyword;
    }
    if(regex_search(textLine, toKeyword)) {
        ++numOfKeyword;
    }

    return numOfKeyword;
}

bool Tokeniser::checkRelativeDay(string str) {
	regex todayFormat(DATE_TODAY, regex::icase);
	regex tomorrowFormat(DATE_TOMORROW, regex::icase);

	if(regex_search(str, todayFormat) || regex_search(str, tomorrowFormat)) {
		return true;
	} else {
		return false;
	}
}

string Tokeniser::checkDateOrTime(string text) {

	QRegExp time12HourFormat(TIME_FORMAT_12_HOUR, Qt::CaseInsensitive);
    QRegExp time24HourFormat1(TIME_FORMAT_24_HOUR_TYPE_1);
    QRegExp time24HourFormat2(TIME_FORMAT_24_HOUR_TYPE_2);
    QRegExp armyDateFormat(DATE_FORMAT_ARMY);
    QRegExp dayDateFormat(DATE_FORMAT_DAY, Qt::CaseInsensitive);
    QRegExp shortDateFormat2(DATE_FORMAT_SHORT_TYPE_2);
    QRegExp shortDateFormat1(DATE_FORMAT_SHORT_TYPE_1);
	QRegExp todayFormat(DATE_TODAY, Qt::CaseInsensitive);
	QRegExp tomorrowFormat(DATE_TOMORROW, Qt::CaseInsensitive);

    QString qstr = QString::fromStdString(text);

    if(time12HourFormat.exactMatch(qstr) || time24HourFormat1.exactMatch(qstr) || time24HourFormat2.exactMatch(qstr)) {
        return TIME;
	} else if(armyDateFormat.exactMatch(qstr) || dayDateFormat.exactMatch(qstr) || shortDateFormat2.exactMatch(qstr) || shortDateFormat1.exactMatch(qstr) || todayFormat.exactMatch(qstr) || tomorrowFormat.exactMatch(qstr)) {
        return DATE;
    } else {
		return DATE_TIME_INVALID;
	}
}

vector<string> Tokeniser::splitToWords(string textLine) {

	vector<string> output;
	istringstream input(textLine);
	string temp;

	while(input >> temp) {
		output.push_back(temp);
		temp.clear();
	}

	return output;
}

//SUPPORTING FUNCTION FOR EDITTING ENTRY//

string Tokeniser::convertDateTimeToString(string dateOrTime) {

    string convertedStr;

    if(checkDateOrTime(dateOrTime) == TIME) {
        QTime time = formatTime(dateOrTime);
		convertedStr = time.toString(CommonUtils::FORMAT_TIME).toStdString();
    } else if(checkDateOrTime(dateOrTime) == DATE) {
        QDate date = formatDate(dateOrTime);
		convertedStr = date.toString(CommonUtils::FORMAT_DATE).toStdString();
    }

    return convertedStr;
}

//////////////////////
///PUBLIC FUNCTIONS///
/////////////////////

Tokeniser::Tokeniser(string textLine) {
	_textLine = textLine;
}

Tokeniser::~Tokeniser() {
}

CommonEnum::COMMAND_TYPE Tokeniser::getCommand() {
	_command = extractCommand();

	return _command;
}

Entry* Tokeniser::getEntry() {
	return extractEntry();
}

vector<string> Tokeniser::extractEntryToEdit() {
	assert(_command == CommonEnum::COMMAND_EDIT);

	vector<string> entryToEdit(SIZE_VECTOR, STRING_EMPTY);
    string temp;

	QRegExp name(KEYWORD_EDIT_NAME, Qt::CaseInsensitive);
	QRegExp startTime(KEYWORD_EDIT_START_TIME, Qt::CaseInsensitive);
	QRegExp endTime(KEYWORD_EDIT_END_TIME, Qt::CaseInsensitive);
	QRegExp startDate(KEYWORD_EDIT_START_DATE, Qt::CaseInsensitive);
	QRegExp endDate(KEYWORD_EDIT_END_DATE, Qt::CaseInsensitive);


    stringstream str(_textLine);
    str >> temp >> ws;
	_textLine.clear();
    getline(str, _textLine);
	QString qstr = QString::fromStdString(temp);

	if(name.exactMatch(qstr)) {
        entryToEdit[0] = _textLine;
	} else if(startTime.exactMatch(qstr)) {
        if(checkDateOrTime(_textLine) == TIME) {
            entryToEdit[1] = convertDateTimeToString(_textLine);
        } else {
            throw BotException(BotException::EXCEPTION_TIME_INVALID);
        }
    } else if(startDate.exactMatch(qstr)) {
        if(checkDateOrTime(_textLine) == DATE) {
            entryToEdit[2] = convertDateTimeToString(_textLine);
        } else {
            throw BotException(BotException::EXCEPTION_DATE_INVALID);
        }
    } else if(endTime.exactMatch(qstr)) {
        if(checkDateOrTime(_textLine) == TIME) {
            entryToEdit[3] = convertDateTimeToString(_textLine);
        } else {
            throw BotException(BotException::EXCEPTION_TIME_INVALID);
        }
    } else if(endDate.exactMatch(qstr)) {
        if(checkDateOrTime(_textLine) == DATE) {
            entryToEdit[4] = convertDateTimeToString(_textLine);
        } else {
            throw BotException(BotException::EXCEPTION_DATE_INVALID);
        }
    } else {
        throw BotException(BotException::EXCEPTION_ATTRIBUTE_INVALID);
    }

    return entryToEdit;
}

int Tokeniser::extractLineNum() {
	assert(_command == CommonEnum::COMMAND_DELETE || _command == CommonEnum::COMMAND_EDIT || _command == CommonEnum::COMMAND_SET_DONE);
	
	int lineNum;

    stringstream str(_textLine);
    str >> lineNum >> ws;
	_textLine.clear();
    getline(str, _textLine);

    return lineNum;
}

string Tokeniser::extractKey() {

	QRegExp quote1Format(CMD_FORMAT_QUOTE_TYPE_1);
	QString qstr = QString::fromStdString(_textLine);
	string name;

	if(quote1Format.exactMatch(qstr)) {
		name = extractNameInQuote(_textLine);
		return name;
	} else {
		return _textLine;
	}
}

QDate Tokeniser::formatDate(string dateString) {

    QRegExp armyDateFormat(DATE_FORMAT_ARMY);
    QRegExp dayDateFormat(DATE_FORMAT_DAY, Qt::CaseInsensitive);
    QRegExp shortDateFormat2(DATE_FORMAT_SHORT_TYPE_2);
    QRegExp shortDateFormat1(DATE_FORMAT_SHORT_TYPE_1);
	QRegExp todayFormat(DATE_TODAY, Qt::CaseInsensitive);
	QRegExp tomorrowFormat(DATE_TOMORROW, Qt::CaseInsensitive);

    QString qstr = QString::fromStdString(dateString);
    QDate date;

    if(armyDateFormat.exactMatch(qstr)) {
		date = QDate::fromString(qstr, DATE_TYPE_ARMY);
        date = date.addYears(100);
	} else if(shortDateFormat2.exactMatch(qstr)) {
		date = QDate::fromString(qstr, DATE_TYPE_SHORT_TYPE_2);
        date = date.addYears(100);
    } else if(shortDateFormat1.exactMatch(qstr)) {
		date = QDate::fromString(qstr, DATE_TYPE_SHORT_TYPE_1);
        int yearsToAdd = (QDate::currentDate().year())-date.year();
        date = date.addYears(yearsToAdd);
    } else if(dayDateFormat.exactMatch(qstr)) {
		date = QDate::fromString(qstr, DATE_TYPE_DAY_TYPE_1);

		if(!date.isValid()) date = QDate::fromString(qstr, DATE_TYPE_DAY_TYPE_2);
		int currentJulian = QDate::currentDate().toJulianDay();
		int entryJulian = date.toJulianDay();

		entryJulian = currentJulian + ((date.dayOfWeek()-DATE_CURRENT.dayOfWeek()+7)%7);
		date = QDate::fromJulianDay(entryJulian);

		if(date == DATE_CURRENT) {
			 date = date.addDays(7);
		}

		QString dateQstr = QString::fromStdString(dateString);

		if(_command == CommonEnum::COMMAND_ADD) {
			if(dayDateFormat.exactMatch(dateQstr) && _newEntry->getStartDate() > date) {
				date = date.addDays(7);
			}
		}

	} else if(todayFormat.exactMatch(qstr)) {
		date = DATE_CURRENT;
	} else if (tomorrowFormat.exactMatch(qstr)) {
		date = DATE_CURRENT.addDays(1);
	}

	if(!date.isValid()) {
		throw BotException(BotException::EXCEPTION_DATE_INVALID);
	}

    return date;
}

bool Tokeniser::checkFormat(string str) {

    regex dateFormat(CMD_FORMAT_ADD_DATE, regex::icase);
    regex dateTimeFormat(CMD_FORMAT_ADD_DATETIME, regex::icase);
    regex timeFormat(CMD_FORMAT_ADD_TIME, regex::icase);
    regex timeTimeFormat(CMD_FORMAT_ADD_TIMETIME, regex::icase);
    regex dateDateFormat(CMD_FORMAT_ADD_DATEDATE, regex::icase);
    regex dateTimeTimeFormat(CMD_FORMAT_ADD_DATETIMETIME, regex::icase);
    regex dateTimeDateTimeFormat(CMD_FORMAT_ADD_DATETIMEDATETIME, regex::icase);
    regex relativeDay1Format(CMD_FORMAT_ADD_RELATIVEDAY_TYPE_1, regex::icase);
	regex relativeDay2Format(CMD_FORMAT_ADD_RELATIVEDAY_TYPE_2, regex::icase);
	regex relativeDay3Format(CMD_FORMAT_ADD_RELATIVEDAY_TYPE_3, regex::icase);
    regex floatingFormat(CMD_FORMAT_ADD_FLOATING, regex::icase);
	regex quotationFormat(CMD_FORMAT_ADD_QUOTE, regex::icase);

    regex editNameFormat(CMD_FORMAT_EDIT_NAME, regex::icase);
    regex editTimeFormat(CMD_FORMAT_EDIT_TIME, regex::icase);
    regex editDateFormat(CMD_FORMAT_EDIT_DATE, regex::icase);

    regex deleteFormat(CMD_FORMAT_DELETE, regex::icase);
    regex displayFormat(CMD_FORMAT_DISPLAY, regex::icase);
    regex searchFormat(CMD_FORMAT_SEARCH, regex::icase);
    regex redoFormat(CMD_FORMAT_REDO, regex::icase);
    regex undoFormat(CMD_FORMAT_UNDO, regex::icase);
    regex helpFormat(CMD_FORMAT_HELP, regex::icase);
	regex clearFormat(CMD_FORMAT_CLEAR, regex::icase);
	regex exitFormat(CMD_FORMAT_EXIT, regex::icase);
	regex doneFormat(CMD_FORMAT_DONE, regex::icase);

	if(regex_match(str, relativeDay1Format) || regex_match(str, relativeDay2Format) || regex_match(str, relativeDay3Format) ||regex_match(str, floatingFormat) || regex_match(str, dateFormat) || regex_match(str, dateTimeFormat) || regex_match(str, dateTimeDateTimeFormat) || regex_match(str, timeFormat) || regex_match(str, timeTimeFormat) || regex_match(str, dateDateFormat) || regex_match(str, dateTimeTimeFormat) || regex_match(str, quotationFormat) || regex_match(str, editNameFormat) || regex_match(str, displayFormat) || regex_match(str, searchFormat) || regex_match(str, doneFormat) || regex_match(str, exitFormat) || regex_match(str, clearFormat) || regex_match(str, helpFormat) || regex_match(str, undoFormat) || regex_match(str, redoFormat) || regex_match(str, deleteFormat) || regex_match(str, editTimeFormat) || regex_match(str, editDateFormat)) {
		return true;
	} else {
		return false;
	}
}